#!env python3
import time
import sys
import zlib

try:
    import serial
except ImportError:
    print('PySerial must be installed, run `pip3 install pyserial`')
    sys.exit(1)

import struct
from enum import Enum
import binascii
import hashlib
from Crypto.Cipher import AES
import argparse

ISP_PROG = '78daedbc7b5c54d7d53fbccf7dc01b7ad4c14064e228189b5a74504824664c44d45c6a9378499b54c901d104af68306d6cc19961444da239ea60b0959828d6b6a9e9a8636312b40d62da3e4fcd45316d1ad101062f091019060d30ef5afb9ce17262f3f6f9bdcf1feffb793b7cbeacb3d7d997b5d7de7bedb5cfd967af2085a4e9bddf7e9e3f2739397f8e056047a4785842d40dec80fcad8f26ab841099813040463e40e68002641e284016800264112840968002641350801c051420470305c8fd8002e4fe4001f200a000792050803c0828e0faa89864e5140bf95b1e5e31c7fe90671bf08b8afe6a7e056559dd28af663bf2c7d85b54c142949547883c7c186363ff08f20e23f2e0c18c8d994656d80b736486a9b5a60c26d6b43b88357d02b1264f63ad293f60ad6959ac357d156b4d2ee2ad29db786bda6bbc35fd2dde9a7c52b4a67c285ad32e8ad6f416d19acc4441fa28481f05e9a3207d3f48df0fd2f783f4fd20fd00483f00d20f80f40320fd20483f08d20f82f48392929998a494c131496977c424a54f88494a9e362429e5074392d2b28624a5af1a92945c34342965dbd0a4b4d78626a5bf353429f9e4f0a4940f8727a55d1c9e94de321cd28f80f42320fd08483f02d2c741fa38481f07e9e320fded90fe76487f3ba4bf1dd22740fa04489f00e913f2c75852ae8f8949914506da878951e609a4c97e62ed0a6cff3931d1f931c3521cc945849bc0308e09450c3791611d138b58cec6700e5b11c7a530bc23a588e7263182635291c04d6644c7e422914b6524476a91c4a53126475a9189bb9b8972dc5d14c5ddc3443bee298ae6a630fd1c538afa71e94c7f477a517fee5e6680e3dea201dc5466a0636ad140ee3e6690e3bea24112e8c79d5c14234d6006bb27140d96263243dc138b8648364676db8a64298519ea4e291a2a4d6286b927150d932633c3dd938b864ba98cd99d5a6496d29858775a51ac743733c27d77d108e91ee636f73d45b749539838f794a238299d8977a717c54bf732b7bbef2dba5d9aca8c744f2d1a29ddc724b8ef2b4a80fe61c9279664a061ec27d713ed2d1e37f43577f558874f22a96238cc55b8195b4135491d8efdbcf8635b6e35b1fe2640804fe45c33b1be81d7d5daf57eb8deef26a9d28db02c85c2b6e71ae07e35f0aa89ada041bb5f91c9d0b80723d749fab5c4c80581b0f5807e8d7cb896a518a26c6d606521863459ced8aee74e4fce277362ae27d953d4909bf837495d4d24e687d88e381ea12e4c6e2121e68d8470334fdf2e4b2bc914b3c4e8f51ae901bee3f4e991b22999601da7c4c632b65c89899324622d0f9038775bd8ba572b17eb264bcd83ca5cc1b01a0c0e524aeb79ca07b91cfb32d9329700f105a2b41e052a822d10c91457955dc9ab67d56112ab9e15d8e3c50d769b7894f84bc50eeb0128e30d89ca8932723305b81e06e54ad28ae60b37b87d92a8ba8691d75fded7b5a279cfd75a1d360e8ed44135c510ad1e9943b47b3387d03a949afad4c1fa8699e923f7f67ad1b14f62c13eb1c7059f5dbe95cccfd4f3bd65c67aa2dcb40d04c22aaf88bc2adab1dd59b9603ef1c7d7747d7b7d16b6635db8aa60d85a5e2162fd50062c738ae9885d59739ec44926adfc8da8b34612291fcb4519208d96c769cc03f3dcd309eddba54a8ba09c22cde6a2ed85be614d073bc7b790a6e60bd7a02f307a5f60512e4d675218751527f56d5f47a200f2d6888ec40c4a69dbee87b62d16a97e9435e7200de8a958003d1db52bd9d0b6c341ce73223b456cb07b5d67893ff6166d8b6d593c1cda723fb66507c8d2694ddf06f3437358e5514f6718b09f608f07b3d6a12dc49c8df3caf4734dc9858b9457f8185958499a924f3c290bcd40ed0be93c236cc5eb25aa7002ae0b713c4c83ba33946f89816b3bf0cbe17a8e5d15e6c0f521b8de7a9f2aa0be4e60fca92ae40b6563fca98e549e984137d67b42d760de62e8351fbaa24a851a9f45fe9c6ebea647f715d4a32c59681be0d8b5ee97d848fd3d128d3388a699186ac036d278d27cadad2c5ade69a1baa6e6848fa0ad58b0b32cd8d9489b71a83f5a8fe4130fc11cc8409dbfafd5ff04d67f8123d5a9c93435548b325937499fab52b996af29540bfd8381bed38a7dc59a06fd44025da0ce45b4113817c4f4f419827d66e1bb8e4ccd7e58492bc1fe03323191fe83f9c8420b8d0ffdeb2697d142e3c86b87915e713a55d089b57f0b299b2f30af53ea22b47d693bbf05f3ef49621dd0425e1ff01ae4b3e7639853886332837a6464e85fd87e90ff09998f41da1e2708c4dfdfd515d1511ffdd0714058ecf3b43c5e2f0feb04b2be2e60190bffabef38a1f9bf6d1c1f7a5e62af31f481717c415ebe5ba4fb5af3212670e01b70d6f41f30e01b30d6945580c1e0536c035fe30e4e8d02dd6f02f022fa4131e007b19ecd90efe607df576ee359c897d7e75a0ee6520ee65216e65216e65206e65206e6d2886d17b05c357463d071f74db021add0b63c2973820de45bed72090fe3b50cfd2ed6e1eb07724713d91545e2c428122894886a8e62d51a893dee1c3ded78d4dd8c2dfa26f1efe43ba0af8420ef9bd6b4699cfcaa883e12d4278b83bec3c83fbcc2989f449fadff63a9df21c4371bfb2ed439f90e017c22a833d4357d30f5a3681f2b81768c827a6e16892d7a1313d72f6e54d9a64d7728bba3b09e02d453a03ec5dd50dfbba1bef7407def81fa4e81fa4e81faa633111d8bb4aed0c665ce28b0d7a90cf89c2cf665a5b64070244513afdb0d731dd44b4a265a3fe0f57ee0d4fb00b461310f6d184534db5084fdee25d5c3533d4ce1b7135bd40d72bc6417d8da8436553a74cb7cd066615e56b1a5577e7308a4f91ad270d6d12dba8dad3ee0382ad171c865ba0fe07838ee09107331846788fbb479ac9a780311de8c7d6887233c6b62112973e31cd6067665de2019ca5285285dee0baf831d6d85f2fa61796566902d31526ee66b3de54aaff52db7f817df2cf7d42f7a971b2953956246590fb60d8472daa11cb1a75e9965dc51cde661d8515dfdaac705b4aacaa38a2bc97173881cdf7914f2aa211ec8df71ea94678ab9a66f9d2c50a771f306415ffb0af21ef8cd3a483bfa96e1deae95e1da76eb328ab745cae8c97f31e67f0ded1bb4cdc7a80f5992b638264bd43ef9d03ec21c046312c6e704c62b05bbed13f707377154880cce4d6a14f4a9500df8626d74dc392a6630653566e09989f28a644a75a24d789ff1ba0e93267262699904fec45f25d1eb76a27fc0948966229bc1df980ff3c26f6a884dba0c76b502e65537936aa63231ca733538070f047f85e57c515a3949e0e304c0ef1b1b22bde705182b0c8c956edbc7ed9b41e4b541a8eb9e3659aa0dfbb7063a68d947c1f7ccc4fc33a19f4e2736771d51469e23d663e0b31e0e10acbfd7dd0afeab44d0b7b01e83f6395c03d77bba341b183380da409833704ec6f1dc44ec7370cea27d909c784c1678e43d5726e01c8ed7858f3611f2c82de68cb0157d523ad727a39e71ee1a3d65ae8bf514d0397235f5b125f0b1df001f7b4d1d891382617998c0c97305ce96f306c85941e8fa2485e9f66b1dd5e00f55431b4127d1da3ef095ba53e2c01fe164b40d4b6ac69ae7a30edc51aa2711ea0bfe4c85d6fe741eea6e7f6877ec03705f05bb2c3b414eb0d3684fb8aa1d508e8bfa48f23017b1e50421bc0fca94185b3dc8547d04cb67bc810adaf794e7426883ba56107b7f39181ad4d4fcfd164712d8086c4bcb0db46170af30da9104728792d067ef80f15c0ab668139de3e205f0912e74f6ba7fd3513d17fa7806d4b54aaf6b0d8473885cec22ea4e11ca6e83f05ab82e66bc0d705d7d96a0cf16d10b575d8f7d90f1e686e03a487cae635fa82e61a26c065d032f4e7253bba6c4822f571d0a2b856d22d88daed70b5bbfbdef6588c45b5d41fdafb8f3da98382e35dbbda120f0c1078736449df887085db2381cca0985a9ef9bb99d70b39cba7c490c37eb0d228f8802f96e00ff305c9730b6a537485c14e8cd3c8b539c922603c8d25b0ef35cf48332be407920df4f5283e1b06dedafc03e0e0aab51384f7f3fa8f90d93c28ecc4fc0d6bd0f639567b472612d36a30ed2c3781b0e3acc6d83702b5c1733b6c56d304f8a68673780eda8e7409f0ed03de88ba0be1ca04fdafea05b07e8536b7fcdf6c8929d28c501b49fd08e09d7b87d0203bedd191857b5a8b35447380cf2de803e27421f043f039f39c0fc993241d0eb73d1c664e1f8bacf57827d9761529de1b0ea8ee2b4fee9ecf43a3d2415ef89252cf5055d74ec8d83753d2c96a3189b5403636abaae9faa7f5a934398df3dda58ab9e82630dfd185847b2da5ab3e61b6bcd26cb893bf17e93858cc6b9c783cf775cc26f34de89713eb05d50dfc2eb1599c9d773f964ad2cd7a7600f5250e688bc5e77d2346b0acc95109679be13797294a5d3e674531b04639fc1f8746c1e81f1b35510753e1b19affe2d524784d71d17f9855207975a44349bc493d74d211acf6a4aeaec1d17d64b68a706407bdc7080bdc034a83359e4d147ee78dd846370e1719f5bd337ca4475ade76b3d44d34751df4820fd572417763f278075647f65643dae2f06342593ef815f3e80aebfc146ad4826e365e9d0007f6c7d17d8bbefc25a036458d8a5ee10586a63617d47ed6bca36eaaba27df4f2478812bfbddbef86fedeed63721920333f14fdcf76b411d4a7471b9df21af8d36f21bfcd318921dc24ea23dca03e2aa4a5fe3fc8e9a880b969483dab94a29f043e7f7990809d62ade5606b60ee905d125dafd1e70c9ba0fea285281b6a58b40d2877995960b03e6535d234ff6df51de85b40dee0f79c18a0c4c2fc595e8ff6ad3faed755f1cc004ce71f7ef6a6c63f84fabf79ef03e3dccaaa3fb16a41889cf4bf7b517d4e0cdfbbe9ed9dfdfdfd03d324ffb29aaec19736147ac53fd93789f80c72e7cedcc2fdc42af9c8eba20fbc2ea00250c0eb3c50c0eb1c50c0eb2c50c0eb0c50c0eb0428604321aefbd59d2e72f64575a7c0d0ffecd9179ff0a93b93885646f1864061e2cd9d8527cdb7bf3c75a77fd59fda7fe1bb7dd5dec671cef1aef175c5d79a48ade4c9c51e18b8f15ead2bf3bdbaf8d8bd3953af95644e3d933e735c87208c6f1fd7da7fc9b6c02888df442cf2de1c8707b4e549e294872552b298aba88a3efbb2b534486e14749a6fbc6cdd1f8c4e368d7241cea33ae70fbef8abcc864533b2a7bac6646d28d4c769d741d74665457365e8a0db2571609797856dae0afbfa611b0a990c88350c4764c6a815a4fcb1698347d58a75e39cd6d823c43aa20a7a03ace453008942b4351da8057ada6881bd634651065898e8a6e6ca80c74cbdb9cef80eed4a22e9b5da959b4fafd3ae7225d29edeb877119b3d8dd14a650627d6c13aa85f993318f63adf6767d78e2f19d5285e535dcefe50971f8c7332ff454b1f3d86c9124c8d603bfa15654079b5a35c9716dd9fbd379b5d2a74d7516ac61aeeb99e3eebb5abd3068b75a36ab5f571c557e95fbc98691d1d7c54955c8f41bdd2828f42ae8f9a729819db20b7fb5a4c4b8419fbe06a4fb3292f6ac60770156e7265a69f8ec88a92ea7abc9c58e75466d78ad7de9939aa715c495120d10516f986eae41f2d5cb27b7a67aa724e22bb1795fcc5b9a8aa9233f3cc806ab054fdea7ddb326663cc968db5eb5bdecac4d99b99deb716270b5c49d6443eda9a0c56228527f1a7ade97065e141dbbcaeede9a0ed0b6780cf031fec3dc0c29b722a5146fcd3a40c5cd8a8683676df251893c9ba5ff47937b77105a91d1fff4c51a02873aa6b43a1a3466046acdadbfee7e68f1a8b3bf6b7fef7b573757faffd71c7d3ed8b5b9f695e7eedb5cd13378d778e2bf9f06fbb979dfc9b0356318ebb6105081e05ccf2035d9b8b66c3ac3a9cc9287a90795031f51b3de2f20f8b7f95f163e88f5ba31549182d08c7a3d74e9bb745c9ff11f3c32da645f17943e7285d15a3a64f3fb25bf9bbd3fab92bb1b889cc1916bf68af6bb66bbf27beb629c63e421922c41cdff4aa5dc8503cd104ef34c5d49ad7d73b52a399fd65b67e657698bbecd6b457c987adbbcae97d7226bb73fe980fbf7bc0f17614711c2e2127a33acfef2f56f2cbc8df8bd317f55f579438af78b66b834779bc3c1af339f86ac9161c07d6b483e4f91b45990d2b67acd9bf467cbe61e98c67f63f233e7be9a7f7bfb0f70576fda565535d51d046a9fd4861d1eca2e9f283fd4e3ce5c352b877b194e9d3f7bbfd5103ba38d08d6991fa9c6451368a0395a5bf30c1f5cc0f4ef173fae71de777b3035e2c1b00f6d17d3bb9e239fefe764e09b61261cc717e1fcc9b927dd6dff6d35ad8470dabd7fa08ea5a0d48bc35b69574ce7fd187e5ab0f5e0d330fcb0f7e1d1ef050c9434ff8fae72d2f1e077662d15a2c7d6fae5c10f2c9b992c91a5b47e2aff167bef41c771e9e165f3bfdfd2bee2b4e25bf935c719b164d2d3ee79a0aa96ad72f98be37f7f63d0bde8f5df436689e6af755db805f746b774773fd91c4622ad7cf0fba9f0af6cfa32d3198ca160a8550b692caf83c053c5868d7827fb24afe2fc81567229400f9173c397d8adb638f5fb4e07d6cd98ceb5a4bcdf9e9139f0ef0fdddadf87fcd5c711fafae61d3a07db0758e17d7b0ace7a0d3ff60791be3731c071d1f2f214cc6c9a8b9deb2e83892735c79f03382adf963da9610f3e1f2cf3e7df7e9e2cfb1bc954f4ebfe43cee764f7bf2fdf84597a0c4b5c79f5ab5b736b1e3d1e679d78aeb66b73fd20a6bb14799e9c2ecce1fee6a1ce5dabf48ccbeb4e6fee7f73ecffee4d23359d0cadc1fa087ffca493aef563e8922ce45972b1df3a318e5859bc461be935136ce67668365deb0636fceac2dcc68392aaadfa7953d65fcb0f1c7755a39e336bdb9492febc1ce1f955c339675ffb37b9fbd55799f6ae5fdf46bad3c7701f367d7a3ae0daf464abb08a52576cc6e2faedb5bfb486ba4cca76b55e75a06e65e765c89d2e599038e59e1c952ea83d77988f2d56156b73b19df9482cd4339407b8b5469658272d9c3ab3529a42829ce6d26716633f12c06bb51dc601e284df6905c5594c2caf04f08595192a4486e313e3b366fe89296b2bd9ed22f875e2dcdc5f710a5016c65f224f83cccb44ab9663bae0a38e62fdb329540c0a4d66483c7e7e622f90f94b4124e0da6f90b52787d86127598c466af2ab3891ebb32a72aba33d595e4590ab14a960ee20e47b15a8afd838eba94e84f06c666afcfd0e3cead32792528af6e28e1eb3c39e0c31d0d0ea4f4bd60ffed4ddbafaa8b8793ed81519e18ff5b951f662af5e06d4b3e6147a52ad905908ea835efe3fa4f7cd1079c611aa71538055251a52adae7828e25bfbfa155ad19cd94b9aba5ef560ecd53668a247ec9f6a6dd57bc521d09be3bd959e43363996967454a93cf8a186794273e570d98497ca0ea882a362728171b860f70304971f392ecae19de9da78890c41d2dd1b5f2e14b8f78cc504feba47364a82283de63ae0d6d0467f5cbb89a24bbf0804df6130e5c7be5e95cd1fb5c2ed9d5d052267278b52da9fef3f58ff4e8779af911cf3d1ba7bab8948d8c12cb8f10c6a8cf4d22d34cf537d5b654a2ec16e3311f35f37af8c6cbe65c7c9e116a77801c22a7d42d26ca082e9e3b1e055ee46622ffe4fdb092fb2af14aafdad7672a0f86c6eaf34b61a1a2f935b9ebb62bebe7276e9fba45d9d3100bf31fab8df9da82cef907372942d0e2951e64943541a2244c49f03c8b4f2cbff333aea21fb9630fb6aaf560c30b4ae97cb3ba339353b68bc31dbe28e6d1ed72f650521840d96429f37907c48e5570854ca59d105aa7dd492a00ee9ad06d943b31f4dcaa83f23c0fd93643b9748a57e7e5c25ab6988b2b86f5e3f0e164a0f8c84e5910c3314f2b23aa4421b3ec5ca2dd3bbc866af4d397680e77d7349aaec53492cbe4cbe78f7c3843a93f45cac4a3c27aec07d0574e11755e35e4d920aef5016798c60901e7396969a52c405f11a1af2ca96a57e7253165c5a7a41ff8629e51c42a327471cbabfe676bba4ccb337c43b363f25a5ef56edc69577e34928b5dd2d37b639a62af503926852e517a6fa8560ec4922d4d5bae9606f6eeb41e1ace64fd41f98b89ecddb97b31f6abdd0df14bb636c55f99f5d644e71defe2488d553a53fd036b6a07bcbb2d49e46ca1c524e742bdf7a0fb9f95aedc4e69eaf6ce82f55b5c47b8c9fd49e73c65f83913b6a1f5adf94fa9c19ba4a4ea64a6d7e5b3bff9aa2bf7bd1df3b6779ad76fb1395d76bd3d7fd8395f191a24f35ccc746c0f9b3077da6b7bc66da7928e0d2dc216a3b9bd3dffacf5f026e267a7d4438fc9cbe45c99d6c3604da15d4b735795792518b5391e02f7a68778ccb930f657ef79dd37ed7fd843f31a157af2c54a472af43d7e53d7b452d7e6932f5b53be26fb6678a5cd3092b75ff5af757fae0afd183ab38c6d390ef681910343096a24b66eaf2651aae7afbbeb7e7b149ffcfaaffade04eb34049ffc8e780f7dfa713b95a15504bd524726acf25ff4750942a7cf5bbd8ffc62b34daa2245815f642a4dd5e4a05b595a0feb00c72981b82497f0facb355dfe2b812e581914ac25ef5e3de8569f7387e562b143e9a8161d5e81387eef228e4a81e12a5d8ce33d81e5de73b19c57e01cc75c9cea72f1e82373c75c61eb3b0537ad87e677580f873aadc7029dd6776a3aad87aac3d6c3b95dd66366b82f85d7fa7a97abb61590fa9bd32ab12db766bd26bd2658dfa9eef25f05890281b60d8528b7d2b00f6556032cac84dc55e4f697fccf07bbbcc5d5641bac8d3acd8e5312596fb6921a12f4b904c7a960d82561de377ce84fe2baa5f8da78e7feba3fd73ed2f168fbbcd61f36fff8daef5d51d76664372cda9f2d2ebdf4fcfd3fd9fb13f6a7979e1d9305eb8b9d07374dac6b6afe7907ac59263bc3381ba925302bf5e3c3f28d5361e5662a87f392fc755498ce4cadb91ccc4ae53709331bd72c8fba8c796e28c4b71afee5c73b94c797dab4319efbbb9867bdeeef40df514c6d445e1b084fdd61ca114a665d86be1475992823db5859e8c72a7ffa95e9ef65de92a5d3682ff415fc56bd3c82580f4f01ffd5199ee71ab549bd3182782f2f65ace929615c091ca9543e582a94aed8fd6c6953fc55ceb799788b974e5b3f43d8ec754f6194b5e7c679975ea539785dcf326a03cc23f5a5b92785792e6dce6b222b6134c04ae29abca91f73af8fc92cca746d523c62bc238587354e999d7bdb0916f4cfe1d9af2a2f4943bc5199608ddba25509a43d756348e992dd79b258129637f14c7c53e955592ae415a5daa48a2bd98fdc4ae72912bf6877637cf6ee6b304f73cac56a7e1af41f9e8d5d54ea8fcd2e0dc42e29bdea7567335867dd5eecd6ea9ccaecce766df65ece65ac8753189471e1a5e78fc42fd9dd1c9fb7bb55911a08da69c88b9b5b19bf12aeb9dded5c2ab4a09be74b3b327c7bc10381f5cb3f1ef6297f5c1a851aa272ba7806358572a3bc2acafb65352f4b2b59657d3501b91a41ae6b324abbac9a4ce321051bbf28fe627c767c7dfc92f8cbfb372f7d17a46e8ecd2b0529daa81410876b41295c3caf49e2004976775c042960fdf3518b4f79e41969775ee992880cff67657f5432cbf73f2b3be12f2d3ed4ddf71ac76df20f14aee03d5912c237ca3ef728a501e2b8db15566f7c10fef3ae0fbd281b95a9ae5ad465e26317c55e8ccd8eada7326555b3b2043eb424304395edfea1b9db03b1cf965eddeb9a87b6f5476f815e622fc7e6c57e416593a0752481fd146593782ebe65e81a087343831ffa0eba47b9d75752f94e7ee0fb73c96b5eefe5cbb497467cb1235e5cd5e313089cabadfba5ed625d53f2ca68b0df2382449fbfd5dd0f6cfb53fc89c8aa17538c773a768c61202e69e69559027972c941cf93dd6b785c01ac2f588f4f20600df0858f5a4f584b3ebf6a5473e2b5d98d8fd489ad6a488a39e81cef9c5872b4645735e65632839901b637af6f6ac7b0d10cbb66efcaff2e9e5dbc77e9fd4b135d97b233b260f5caf795a37049e77c5ce7287141d2a93dfd80d411895cb99fd2f0ccc64e1fa65841ca07476afe7bd7e4625afb0ac9496bcff5ae7d85a36fed61c54f7d99a4ab8975079d1b157c2e31de1955953e6b97bfa9367c03d7f6aa59e24eaa6a9b9bfcea4bb54d624abedc97b9fb04acbd785c7bad9fabadee716dbf6df38eeabbf2b67da04ad325c5152489d0c227eec5753c6a01d6f7440df93a51f2f83c5883d1559f9b68e192260ccb12b1802ec7e2f3e0bd2e4c5f9e45f9213311aae3e7581379fabc3cfe93f83c26e3d3eb0e33de934c932e44d68faeca92cd4ce6367fec9c05efb75476f707b4126f4aab512396f9ba4672b5f728468d88d7463526d6cdae2db9ccccc2bacd76459e5d600b4774735f23967de3822ea598be641cb47cff7589b8ee7e4c20eb9fd0568e92a9a4ba7f1ec6b17cd7a81b7217eae6ae17b655d1fbdfc1271d4db5175aacb13c69aafd79cb2c9f2663e687da73ad9fb7e11332ec77b247e2a6a989d7c456ec7f301713944dd92ac608b9b224157efa7269063e39531eae25c79e5066d7c29cbf7725bbe652f6fd4bf72ecd80baa465afafe17e052db23395b396f3a4ff3ae5bc689ab304a5deb6795775fcf4db5f4acfdbf5a7f40540b3fbaf2b9a8135eb9cef9807bdf2f620d96d5f9ba93c1d32a54a768b20d9e68788b079d25f3a0b5477e8e7b179c197cb3c12834f3d636bb155b0ed654f2aa72c9088e64dedb66ff3cf6cbc2bcf5a3e9a39f2616fad94df865a29aa6466b8a04c16ca1c57ec1f166cdb6d3ff23194e90a90273295ed011e537ed7a70644daeeebe70a3ec71f5c8c7f4b5433f707810cd86c3d3489991275f7b4a2ccc1fedd3f78f27dff424b13aea7ff5cfc48316da5ccf2ee270feb2b0fee9a12f52b7b4b253318c7c038e70868e792c0f8baa6da3d1f684f2677558f72dd9f7d6991801e40007b0693417b858bf9426fa7a391674f42c78ae6152feaeb8423f499e4cdbb578d6a4f6c9dddfcc8b5f17562c7b812cf13b8ca8b9ad0d4dc14421f716f8e33d7952b3b439d2da503b6246e9f50b87ee8445e79aa95e033b5bf05998c4b2bef5fb3770dfbfca5a57740b945dab3b6b730ed7e8f7f64b0c32b14da9177e177a0e3b820abc508bfc9418c4ef022ebedfe7fb4ddd45694c5bfd9feb4b6421bf6225d8dbcbeff705625e655b405bd726740935ef2ee7e60b66b8ad36da763a8dcfdfba6e6415f08193b2a954261246a2bb16e9cf34da778ada979c555c711984b8bf9b0fe5ec6aa86eac3b6e23612bb984b131857c9aa3265c469fe6d4fc965cec78777bdb4f599b73da5cb63bf8cb92a832ca50df8eed13ab9c6a27940d57b61957325a08fd4c0812dca28a84fe555ad1dd0e6ac68fe7e5d6c033373c745c7113eec5900f13636c6bff8d29b9e2dcf289b8645398e1533d6778631d663c7885768205b725c253beadff6c43ea3dc6e123d73b1adea4728669f689e47d79fb7517aefb9d8d22f4baf083335a96cee3612eff70f33b5cb12ac65b343bc5c934af4d56ca6d7534d62723e7c99a64c691b5a26994999c74c5e920e7a4a15b9580a6f85bc60e66b7acd27ccfc6d65cc725cef3ce593cf79c8e04c981f21b75c7ca3c8c589667dc5366ea7aaadd84aab445766dcfc24bbcd1320f1394fbdec410dbd1518b8e5cbad57b65f1efae5d9777f90a9e45443faa3c2499f8c6bfbdc6a22d7202720ae47ce308d836f2d0ba455952aacd7645cdb5fae6a976b929838a95a7a02d76bc555d04eabcafc57035dfd61bda6cd8ddd730ab6fe6fa5916841ed837a5bd0dc917d2de8a45589f56fd2996cd465f18b894ee533581dd480fd2f1709dac31de0c310fb8003c9a6c9f8dc7ff0dec5fe6942d75799f7e60a39375e8ecaddf5b2ea8315de1f31567cad2c08857796c3351f5fcb645c59343bfb60f680ac9e19586cc539b864f37867c9df54698d145f1b7fcd95197f2662edd0d63d4e92c13e82bfdab637675c09d898da3d7f67bed0e70ecbf35fb794d177cd68452a3d017c7358bdfe9d6b681b4cb5a66b601f4c74f6cd8d3ff34ae6b6bfe11c4e673613f301c6c1d4669a1a43f816d61a3b9a99e6fb86f6de94a2417b96953feb33ff447fd31fd152d1346f48264c13b3ba779a4ce9d669688a5f4b3ca648ceef9d2289ef9b02df6529972a58d53c86a40a84b5094142df9f634d2cc1ee7d88b2d01cd6f61141fc2b15ac3c6c0c81950deb7557e0bb4f6d5f5581992803253d1c43e83edcf2409ff474ff40642f9bbe87207fb4bd25ff4e7b32788fa6eb3eed9d28ac04589b54c7622f875658a13a2562c3779793dd8cb24924dc2489ee850b148a04f7bf5d2fe0937d49f4bdeac6eb1552727ecc2ccc8ff54aad2ceed5806518a3c448bcc3e726ca16894dcd24ac3c3485d8f83ac2a5f2747f8335f99befec6597d0fdce1efa1e95affbfda1feee91ee0d740a04eb71bd60ba263feee9685ef8852e7f963c743ac1f7b6b8575229061d4d72d2bd6446b9715f72f71e10174f7abfc3c47d7274bf67ca60be896cbd5f1609afed6764c04bf8fe7bb8c7ce6cc3ef0b1e68934d24469e3fcfae0e4f24eab024909de1bde2c7c4ebf2e1fe1ce2382074c86bdb98b2736e466d1b4e8eefdc4fbc0dc3c1ef023facfc6c876c4ec2bdeebccddd401c895247598d87a17b143c15740f85d512ea88138713ff48f106e44dbf594815be3347160b09eecf55872711af7816f23a3a10c26071ccd85f31dcdf1f538d7b11f93e7bf1a4665669a888c5fdc6743f459e6f38beeb55cfd6331ea893eae02e7b1d5ec89b23d8f7e2ceb988fc5c1b291b524cb8531c293b5745b01eb6c5f388ef2b18b91bbe0a97ed3c456ccfcd03de395276e12cf135003f9c00735e5b38ee423df13e07fc3d1788ada18d519f4b20717b1a88ef14c4c1fb35417a3fb5381c96c5b6b0b7a181c1fe1d670e116fdb29d0550329fb5c6070df8ddc3692e03ef5b8cf873171c345c6166a20dec560690b46821537439c2eba8fbdac6612bde713db6919b8071e755ae649656c0535747f44dcc7198c0ff7f63e2783be20cdf9b94cd9ce198cb720446ca07735144bca76ce8738b561bc8ffcb8f3390cb6894f3c11960b62b5fa9973196f48dfd775762dc85048655083c3883704bc1dc8fbce1c6fc0476c42051debf49b13fd3db6fede3a4cdba5ae22ba7bdf0f8edd5e7bbfacc9d3e81e71dcf7a3b71b0fe34ec26f56940d411ef781a5467f678e1a45b83801f7a48c215ee926dd83a08642839452188fe20ea20a8512f82b17e2a246107ffcdd9d2a8c1fba37a379cfe728d7adf6d270f73054de9ef1d9dcfd1efe7a92bd45764bbc4f24fd657301c17e6cb5405c58fe788b1b482a8c39dceb611d25814f2ad13daaa912e9af0a31bcbc3391d8c46a882f92b262e8e3a547bbd49dd534be4fb440bf8674388789e47eafbb86f5cca37ed438b8c6f7fe3cee79b01e96206d1bb491c4f84bc52e4ca74a7344ccc3919849f71829803268437fa9d4d57b4f48c4e682ad85d574058f75899b3fb77b2f9c0a46c52b8588ea49c27af447db853617f7eb4dd9b19fd8722a08eeddb19657dcca7ef34a2dda6f8105fbdd5f9e5bcfd85cd8fe47be1197ead02cb0b260e1210e2d5bb7677136a11ec66106a169938f80bf53d59d1ebfa9903d999cf2b83410daa6036c24eed580beb30decfe1d9c357d0283795f079b4fdb673be8d6590cfab7808eeb88637426e1ee04fd39a10fece459799e08f22642bbed278efd22dddb84fab35a70ef5fa1293f3533399fce19161e74c1eafbd6067bdd30be3c995457f8184e8906bb7b04ec1bce1b44a2f3860fcbe4615d326afb37e790adb392ad96d10cf43dfc868783bec742df63a1ef45e6059eeacb25e8ed55cb806e87a26ecda9d46fbd40f50bbaa2732a8e3d7780ee8d4b95a6cf7124499c1a0a84bdf53e621deba3365ee367f6f0c7f5e597ed1846f754e29e921ede185c4f032fa1b38737097805b867eceb1e5e068e39fc8ee0660f6f2ef23a21bf1b3dbc1ce47d0df9b5f7f0d6222f04f9b5f5f05cc86b82fc823dbc1dc8bb00f9b5f6f0f621af12f2bbee13815731834b0d85c3653b8ea0ddfc39da235ad78347695d2371ca7654118c276b7bdf3a7bf867492a7867b8dfb287574f52ab91b7b057bc204975232fa1a387a7d96cc8ef660f6f18b5d190df8d1ede18e075607eed3dbc49c06bc7fc423dbc0ce0a1adbc10ece1cd055e2de6d7dac3cb01de09ccef7a0f4fb3cb90df573d3c17f008e6d7dcc3db813c6cb7a61ede3ee4c1ea72e197bdf482bc5ac8ef8b5e7a41de09c8ef2af2e0ba50d70d2367a25e1736aaf8b40eec4c4fbf95def957fd56cb17c72ae9a2731ac8d6dd7e6f1cedb6015abc53b78eb7ff28ee95d5db49646e19e700c659a8f75d37db3d1e46fb704fd68d08ff9be30179c6f1803ce378409e713c20cf381eaa7bca4ec4b213be8af0bf5976f52dcaaebe45d9d5b7281b79dd6347ef87a7d848fbd131400a99081fdb31d286bdf48af235f6e4d75d97d61e5e64cc265cefe145c6e78516da4760cc69656be313f79ef5c8a38dc99e3180bcc8788c8c012ab73e1e2363007991f1181903c88b8cc7c818405e643c46c600f222e33132069017197b913180bcc8d88b8c01e445c69e3606345e64ecedb9d2c3eb1e53977b78dd63beb187d73d461b7a78dd63b4be87770479ed905f5d2f5d45c6a8bf47ff117b7be1d31e5ec4deee39ef031eb72f9344f683d36f8e928be85e70bab7bc794f29ae37707f37ee9f54f5ef17a10c8f2a6d6595c60a5364cf659c34067cba958cd21860712de7f504a09d92a09d3d5d36e847b88f3935f33ca1d7f1e06b847289b255e2b56f77f6bc6bfc3eb27b7ce2f3dbd12116c7750f4fc2efa7ba7a8559ba76d2fd0bdce31c27a1df580ef36640b33f49d4fefcacb7fde96b7b44560dae25711e1853c1820e6f7d3dae29d8be76271207f41a0cf9237307f2f05e9c6707f105c1cf770513bc411fe9651338b50dfce0a004e31dd62e77e178dfa3f7fd624e0d0621cf0cccb39996fb1b1c6f7bdab5bc61dd0c71e23cae48de83b4bc23762e92be8af886d1fbd8a7ba9087f7e33c0283717c424b6f7b82f20c02799aa93ce3a93c2d7a7983a03f69e5093de551b9dea476a009e344f2f6090c8e694e2b37d246d55cd94e17dc230cf2757975bb076ddebb6c8b8f073d5dee295be46f5976f95188b730f0cdb2453e52b6ae0f1ee344ee47caa779bc817944c6879b37ca48e5194de5a9d5ebd157d644bc9770a1979e6e2deb7e2c67cf67b7d0136fd013ff2d7a12fa943d16cb5e58d34b4fc22dcb3e80655ff8e4167a12fa969dc9ab3b058ea63f2772ddf51f45eb7f2612a76ca7c0a83bc09f3c2b70d8af22ba833534e713212df0e97ab01e655f788dda854b15937bc69cfbe1de632e622fd0ce44c65fc4be50db42c843f88cc7c04b83b502a1639ec7f279967eebb341a26b1afc16a18914ce443e962bcf1558ed7ba054585398896d6e90ae5b8cdfaac84221dbd49c10443be8a880311a0ae1de6b51e3efb91ee197d56462ddbaf4f85ff5f0a91dead0e337f7f0aba98fa1c76fd2f812c8948af335ac03cc64cadc208ea59016e7c2b59e38b944b365c85f78b587efc13c3be877e68424f594e5c63ed38afa011d8cd1f8d0b61e68b31a89c3b95dbb77c21ab98772a30cf80c19d286f4fb77e879623febd0e60649c0ef98e28461a4cc3c8ca4566d61408f0770de8fdc8f9beba2f242d9c95afa6ad4e3209cdb9b88fd7b119e2e6717c83ebe8787fa5b08694fdca5f370dc60f83b3d61ccfbc4b84818f26122e56b7ad19e75281bf13b0589daeb15e404955506592365e137ce204f22f603ec473de96bfe8df412caf115ee28fd66fac0bf993ea119d28ffc66fad0bf997ee11790feb648fac8bc148907f73f52f13b974bda1a5c1b7b99965bcd7791b4d0d787617f82f1c56ae36b4f18fb1de4ffa748bedddf8ae963963ea735054d0a1158fcd6401183d1f4f98ea49f31a2c9cf51b9f7bbe9390cd0a788dad646e2e66712b920c4e177e732bec3a8a9405f84d601c72996f79834eca38fdde2478f15846efb5892900ef9d82d7df431841fdb61fee3adc6b15656353de701dafa966551bf5bb31d8c2c255339a17c066d847f63e0a6997e9757cdd2357d9584cf58af3a12798683f898269227ca4cf305b9519648fe283ff64b5a7ff011f393ec2df4bbc321928836d36a0999f01b706568107dec34b0ef90570877fe0ef2bf2a7561580d49cdaaf60c947c5b1cfcc6cd6ae979060371edf83c0ee70ef41b22edd5d43ce892af9ac1e70e16b4373ec813bf7f44bb8dcf05fb7ecf5741bfe7a3e766a01f768bb3331cbdcecef0b6e1d9193504f58ecf1eff4fcece88d877ac8362a2dfdf6872d77e3f7cab73347ac78f407b4e8dcfa645924bb83edf11aa3c0b7e2c4b6c4ea73df25c11bf21c46f8cd412fcee9125e07611d555b50e9f3f369da9dc218b105fdc69c77c6d828b349d58f8dfcac828d1934a9f4f05b4756c66c03383eeb3bd086388c1ef39b9c4ea307d0efd667597bfb1a6cb9349e35fd0fbd56af08bc5a6da3d25d0af1a3c550bf10dee06337e3b24b856e15cd31453785753edc2b7c1cce2f761939a62ec139b624edcadbaa2e8f770308f7ca9dc1645df7f807eae68f46f97cda7f039f28c377bc56b82b4dfd36491baf4fef0357ee78edfc163fdb1eee631da776d9e31f48b950bf47b78fc561ebf89d7bf95d7bebb14616e9863968ba3e8b7ce5abaaacf9b48cc70cf9df4fd72931c15433ce3e07ad3ec0b72740ccb1d73d23d898ea3b076380a724951a44c807ee38a26715177627e43201dab3a79c67fdbac4ef39d78165149b4cac710f3383c8f68f6e76a540ceb48837c26435bb979f27128edcc63a1c967ca5cad61b007f7cb423429738e2649c92d04ecc4fd5c5a14c34d8e228fb9a54b7254142bf33cd344cef4f3ff3205e6e1ad8fc1f534d93c897892e8fbadcfe8b34daf44fb8ca3c285f697e502e0ebe0da655e90555a1b58cee76254d356fca69875c0b54d0ad9ad87eae9b324ff6d4207527c2e886b724cc335084cdc4691d8d606596f4d352b8b84533e3331dc69ec732b0579dd4ca2f026bce6d0661c2f3e455e1f5483df24defc7f950c159a0cc7ff07321c97425486290b4086fd023305ca57451339fe3f9441ebab590c3745a4fdd091348370e38a617dd90fe71f7e4af415fb1467c0ce659e2291f31be8f90e78b603f6e194554ca48f63ff75dc53cc28bc44e2f8d6b0a39aa7dfef2a9b246873ba17885176064c717c5d5879dcce4ce1a74f73f87852d60fe37950172647d5fb442916e8d80a144691ee3cb6802e2279bc123081bde0fac4110a0535944994ed127d0711b9a74cb74b783f2e9ad7e6fd5f4aa26df333e0e31646c95221f1ef0eb4a1ace81b429e6c245dd9e6afc3fe992b615ddb1ac6332594c779fa5e650a1fc273688234ed2b819b2807ce75bde5898bbe19a667061878fe99f67f911ffaa5babc1b9f61d44db7114715c4df2974a17eb0ac385ed7516180ffb7cec788bc8feb5527aca77f6621b67b073eeb06b9b7ebf6b258159bc374fec36f95774b5daa5b24bf4b0af5c7f020d23cc8ff8ad445bf373f2a50df43e6b612d93487a8a672220f2c2730f776aa5da141f4fbefd51f736552f0067d2fb4b89d2d9376dc54ceb713eb215c7f36d079648af4258b733957d5169e023e00ca0469bea6736f5e3b5cefe8a0be3ec3b459d38a4cd6946d266b323d774cb2a64d93ac293f90ace983819725e1b3137c6f6b4d390936116c6814c3aa516758399a700efcce157c056bca45d029c8b653c06fa8a3407f51a03f3cabcb04fa93407f12e84f04fd895066b4f61e600eab6c0cd2ef5f03853c5d6338328b38eb7886b5b20c0fbe41d8dabf857e1b6ab5b468df30a7035f4c667b978bfe8d32441039488b3bbbf07d119d9ffb5f6465a99c5546d68bb23b498f6b86b1124de4cd9b88757c0bab7c06eb3548877be4302ce7ba09a6c31dca4a745054a3fbe19954619817fb29fd27310a9bc1f9e3cfb6a39cb807027d5bed1ad3b5e07387362d8cfe534298b61be85c96a03d3747137f76e8a62c9e64547ac6c6c28b8ea4930c964bcb1b19fc46bda8fc43ea4d7de44fea47d4e8cd44f9442211dd70490cafcc94345942015d6fe5922c82cf161f14315fb518f3fd90e68bf65015b78a0a7b365a69dc3f50150b05853f3b44b9b4dfe427421db68d2acc61fd6cb001fac9c5def5501685d89e3a249ca57500195057b8feb25a189ebe13154f883806a9be2643f9eee104bfd3e6aa445abe3fa6aa5d7527eabce144ffd6b70dda5754a2cf0e5745bbe88f39fb77552c27d6d11ff2ca3c493b47ca7291a7e721411b82bdebcf1d8926ca027c3e7686b509e5762b0bb208451cf8212fcb22e803bfc3d6f61954407b0d81fc254584bad6edc7be053a080e043925ff6dc18f22edaae90fd712764615e7484affb360ff0b257fecd933b02e60501eeabbcf28021be4ba294f1a067efad130be73b7d5ef87f4afb1d837be55f6dd012a27ca0ffa0ad26fa25f09b0ffba3e1736e1f3686e5fcffd297dee276c83fb2d980f8695ed5a5eb856c433a6aca35ba81cf4bb6c682f948196bf8f96dfe1a88aa67a423e8e47ad3d13fee8f0819f2349da984983b2605da444d710ee9444dffbe25a418915bbfb22b4612337b948c0f318ace927b12fe03e9076ed9d66af779ce99aff6a4d790bc6f649f0d53e049feda208fd41b4a631687fa4eba3ed2de6143cabcb3911daefd78ea4e9d0e65234d6017d57ebb85032fab2d6039e64b4a3f4fa4d69bce6df66decd1dc1f7bd453c9ea1a3ed34acda8f6300fac621cc1b74b69f4b7282deeb0662de71ee50d83319f36818a73c544beb44cb191b1a47f3de2b8dc37222ef3a296fbf34b6cf3b4fedb9af897e4f8f79bf5a479f91d07c468512f13d60b7ac07a4d1788e04961d991b34b9f6eca675dd2ed1774dd6c4542ba5a33da330ad5677c9e2df2e811ddfd34ccbd9ddab9cf1a19138f650f7da1e850b1dbdf413af9ace08bdcfd3cc271877cf573defb6f573d5d28a4d9a8f290da6e77215b40fa26723544b743f0f9e312543a65a9cea91747f50ca36c2a58a12ce198e636093261783fd29961c939c02375914b949bc80fb81028526dce706f9bcc1e31a16d7aeb86e877e0bd4de7d3e814dd861dcdf7313cf83a2673fc118d4d618ae3be8b95069fa199b7856541af8fd78b6543acc5be0f7cb3cae83f7d440df64a8ef4fd30909d0afce69beff0747a9ef7f1bfafe9b12fe2ddfbf79cf47dff4fd677d447dffdbd0f7df34f27fc5f76f4ef82bfafedf76a655e40cab9e7323047ac6e4f531e4903911db79c6557d9d7415f2abc6e70cf87ecc5a1e30617b37357fbf6405d1d2cb7856a1083a2bd675acad93cab5757842235d83c0fafc5fc775edd2e22eacd7e25e68fb967cf5b87b2e6971135aff755c41d5e25eb8a0c55dd8e2b820d033b9d4f07d601bc23c9e4151f67935893b5f40ca3e0a90b8cf4324ee02acdf4353f1f98ca40e04fbb8ae9d29db00ebe99f753171dc2072fc975f12e5a510876757a983e8d914ecb7c8bb4d97b74697f7329e231bb7b0d2ae8883889c7ffd3ef981eb61d53c9578a5367aee25ead7c65da73ea94d7a1dcfcd2271b193e8f98d363c37270072427bf9efaf09cbc121442e4d439f86575e1944e70b65c3972c3df7c58914e4dbf92529f3b899b21af7349b49c467ca6c640739f53bdc127d3ee9df30a8cbb65164acc9ed44766574bf1bfb46bde858c8f8b9debe7fd6dbf71f8e1af447b5b1a9fc127ce30302491d8967e80eec8aab9909e5ad23b69f7d897a853a5433719f9f26f2ba102317fc0ce2c490b88fdbc9949170bfbd5d827c58d00d437dd8971a5975e04aad2e9bce73ff5a26e105bdbd4fe8edfd11f58fcffa40d7787e51e83efc624c8db511af6938c31d3111dc6b290f03fd9924d6e6da4fb8c99a4f5d363c15d6a1e788b5b046df676726fee15f7645f46213f6137daefa57fa59aeebe798ae9f0f7a8f35d50d8035e2f5b130deb47e724c36413e2e13d1c79f97fa2438d66af77c84630deac07b2a16a27d5daedcace87e66d43366453d2fe1b7a083cdda7b457b38f2dc88daf3b72bdea767b04ea2cf5f9e88bc33a4cf361b2bf8c8b98c68736dce2a28f330f85be8170a3d672f0a62b77dc53340d1d7a7e7bdd4269c067b49f0cc177aaea65078933edf8a164cda1c2bbd837b86b00c5f3521bdf77adaf0d9e36837ac7baab5333887f2acea9158f9719ec533ba6c756ec87fd0155fe8260b7dc2747c7e904dcd1884751d8d72615c3c37cf3bbf95a5f3404d05eb0d55b0d64375dad991875bfbce0bfa9c05eb5b6dece2de23b150c4398eb6dba13dbfd4f45778b3d73b135a1ffa2c453ba324b3f7b3149a8f50aea5affddb2fc05ece71a4ba05baef373dc4d335ef56890715cce17c6e4ece0dc558dfc133d6f019617518cf3cc36bff48a90bee33b4fc0258d7bc13e839630cf2c3f36c0eba8a663033988c1564d18069838b32b4f356f0a4174dd3b9af6f54d2976dfbd3060f9ee962b9e3f955daac9bfbeb51cde6d1e823bd7fd0e674db23df580d70164d9f5a72f109e5b6ed04bfa18a7ccb027ee908af3b8573f17ff6387ceef006cf8d4aedbb042d97ecfd1ea0aab36e5fd1163cd3e44527334b31ef62b5bbce37b4bbfc1b455b9a48ed507af7f65da4f79925f18b84a83bf80d9ecbbec8d58d4aad0e499e52659c036a9c50125025c9023a1c599ab97ee6fab9caf4465e89e6583936954c2db5a69d8e711c90260833a60c6f20dec6d3e029992758d3ce5b4a6628e68ff1ccd809735fb21eac4ec6d3efa2a481d2f6eab73d5bfcf8add953ab747d6d7da4d5f10789682721e0a908783ac28feb0e6e1abf4978903b5c92c09ce6a777a6c8bb52c8a85dca3f7972eb731894a86868a3a83b145726d3999235e2d3caf8e98ec9514c67aab2d3c3f2ef07cf7149fc8429c3cb88e33b4eb2ea652629aa69c3ced8e90e5f4982fff6821b7eb1b56356a5352d8a7c5a093edd04c7a4a8c12d2f17251505e6b936ecbc58391efe7f50d9f30c583ba5a628839901fd21b32853356de5d03f1be744af7f0a5f6887d6b79093eb1f8f036f3db243bd77fc3302c6579e1488b6d35dacd3529d8827279972a61cbf3810bf78d339eaf226574f2a22184b3931024b49746dc22f0f8762693ddf138c58751729ee686afef9d79df31531482666c4cd3d6b174a1a56e289287a1df49c6bbf29ff50cc7994ab67477eeff8cddf8c3fb86f7c3c2348eb87ce9fee5e84bd14cfdd79b189c988e841352d1aa0e7f2633c57900cc09c2097b97dce084aa2f6f3b98d8b30ddec1d1109bad3764b40a25082dd8bd6cf9dbacb9f30fa6b7c2365bdd7bd7aaa2ba3b2aff48437a6b50b7da5df9d658d0992dd39d6c1f07fd693273f9b65c52fbc1eb60e0b92cf1eb60e87eb555633fc7f3efef23b59e997773f955eff4e4efc17ef2c8b0fbeb336be53931bfceda7372a1b0a57fe3a1c3e06b80678f237e1f0fb00cb6fc3e1f6df85c3e4105c033e83ebdfbc150e1f003a2d6f69eef29c6c4bceead52b56df114d7e343de3feb9994f59962eb7ccc8cbca5f62797845760ed420c2a74ce4dd035196aeb1accd5abd06aed6e4ac5ebb72cdade3e5ad58b112ee6c250c8005700984f0f71d22024004480013200a100de807e80f7106000602060162c8566e30d021905e063a14e830c070b836036213b6921180db00718078c0ed80918004800570076014c00a180d180348042401c602ee048c037c077017e0bb80f180ef0192011300130136400a6012603220159006b81b700f600a201d702f602ae03e801d649d0672de0ff401a0d38166009d0175cd04cc04cc02cc063c087808f030e01188f37dc01cc00f008f821e1e03fa38dc9b0b79cc03cc87f002c013801f42f8478027014f017e0cbc858045802cc0d30005900dc881fb8b01b9802580a5c07b06f02c200fb00cb01cb002b012eeaf02ac06e403d6006f2de0394001601db4f0f3809f007e0a7801b01ef0b3985de4e7804240116003c00170421a17a018e086fc36024a009b009b015b002f025e02bc0cc0bf6d805792771115b01dae770076023c8052c02ec8eb5540196037e017805f02f600ca01af01f642bcd7016f00f601f6032a807f00f02bc041c0af01bf01fc16f026e077804310ef2dc0ef015ec061c011e01f05f800c7007f00bc0d380e7807f02ee03d4025e004e024e08f803f01de0754014e01aa01a7011f00fe0cf80be0af80ff02fc37e06f8033800f011f013e067c02380b329c03d400ce033e050dfd3d6117f9075c7f06f827e07368830b406b21fe45c025801f5007a8073400028046c065c015c055c035c017802f014d8066400be02bc075402b200868038400ed801b809b80af011d804e4017200c20d875b5df631a193d470f3f4e431f85c3e980c7014b002f00b602de001c03fc15500b6805983e0e87e30176c002401ea010b01d7000f00ee00ca00ed00ee8ff09d83d400a6026e049c04a8013500a5804d808f80de00ca011d00188391b0e8f06a4011e026403d601b600ca01fc394853130e9f00c4c2f561e0dd05d40e5800c8031402b6030e00de019c01cc84f87540db01fde1da027812b012900270024a01a721bf5a40fa799011b00e500a380cf804d00a88f9341c1e0b980ec8036c04bc0138016804dcf58f70780e600980ff3b840173007980ed804380bf021a01317adc458035801700a701b580b19f810e00d98076fdde0b701df399768df787fe1328201de0046c07de21c019c01c082f02acfca71637828774fa3ef0f9cfa11e803a08f3ffecb9ff905e468422d2219e05301a7017200580fd68edf22559cbb3f36046a393923e2b81cd5bb13ac792bd76d9ca7b2c63f2a30979206b6d7e8e2579dd98e40993f3d6dd65c998f34077086e93d539b93f1a933c31fba9b163f2efb4dcdb2be6bfba134dcb59fcaf6e8f1db3f8cebb2cdf7a1b7398339314925bd08eefdd9a1fa129ff37f763277dfbfdb1cb349aac53bb4e67ea74814e17e974a54ed7e974a34eb7eab45ca707747ac2103e6d089fd1e9799db6eb942cd7688c4e63756a378467ea748e4e571ac2eb0ce1424378a321bcd5102ed569b94e0f19c2c70ce11386f06943f88c217cde10ae35841b0de16643b87d795f7d92157dc3264338c6102e7d56cf4fa7870ce16386f00943f8b4217cc6103e6f08d71ac28d3a6dd6a929af6f38460fc7ea34cd10b6eb74a64e1719c24b0ce19586f03a43b8d010dea8d3ad3a3d60081f32848f19c2270ce1d386f01943f8bc215c6b08371ac2cd3a6dd769ccb2bee15843d862082f5e936c413a638e654dceb2952b5667ad5e9a938ffc0916edfe449dda749aa2d3493a9dacd3548de677e7979ff51c9863307c4bf3c122e76b93fee27c3ddfacee7859ab73d72ecb59be26ff7bab73d6ac5dbddcf25c56deda9c48fcac48fc89df88df3be2e22c5dbe2c5dbe2c5dbe2c5dbeac887cddf93c766bf9f47cf2f57cf2f57cf2f57cf223f9a4e9f46e9d4e48d6e9044d1f6991721eefd1abe581acbcbc9cd5f4be9e6e8d9e6e8d96ee2739ab57e08d9959abb3bf5bb0743548882cb23acb6241fea39a8ab2b2b357e7e483ccf92b35fe636bb294672d2b57d0d90fc2b93a3f336fc5d359793d37d6e8fcc797accec9caeec54fd6f9b712167ed01de87de80e94e6277f6f31aeeefa2af17b8b57672dcbe92506c9d7d3f58d076b12bdbc196b972b6b96ae58feedbd00e2ebf9644dfc57e9fa2680eea0c54fd1e9249d4ed669aa5e8f89b7922f9fe4ebe9f3f5f4f97afa7c3d7d7e247d9a4eefd6e984649d4ed0f4a5e7b346cf678d9ecf1a3d9f4373b571f8994e4df3f4795da78b74ba75be46dfd1e9a1057af8b16fa74f43333f4bdb23329ed6e6adb12cce59a32ce915ce5b9195ddfb7efe1a709b68782974815ce8404b97e7af59bd96aa9cf2972dcdcfd21f21f464d6974f33ed15eec9f43f7ec77ffc8efff81dffbff63be27e943cc53661598665ec98bcb577c2daec1ecbf7d7aeb1ac586c5996b36cc5eae7a321c23258183df6c4630f4c7be8212dbe6dd9fc5ef1f39fcf5f98b36ee91a8b82f354b6e5e9e72d74b537262fdb52b074cd125c63c1f24ccbe816e5cd5d9ebf76254c766bd0363d9f8fb960da7b2c59c9f78ec1155fd6049d4e447a4744a4ff673f5123bc812df40eb02cf95ffb49ff93c8dcbf1df3337dfef98d3ecfacd4e7aff82734ba46a72b7fa8d1c37abc4f747ee437f3a77abfd0e98117fadefffffa8fd1e940db7ffd3a65c8b2866b9f0ba41ff9cfef3fbffffcfef3fbcfef3fbfffdddf82f5fafa49a74b0c749d816e34d052033d60a0c70cf4b4819e37d046036d3750d3cffad258031d6ba069063ad3401718e812035d67a01b0db4d4400f18e831033d6da0e70db4d140db0dd4f4f3be34d640c71a689a81ce34d00506bac440d719e846032d35d003067acc404f1be879036d34d076033515f6a5b1063ad640d30c74a6812e30d02506bace40371a68a9811e30d063067ada40cf1b68a381b61ba8a9a82f8d35d0b1069a66a0330d7481812e31d07506bad1404b0df480811e33d0d3067ade401b0db4dd404d1bfad258031d6ba069063ad3401718e812035d67a01b0db4d4400f18e831033d6da0e70db4d140db0dd4e4e84b630d74ac81a619e84c035d60a04b0c749d816e34d052033d60a0c70cf4b4819e37d046036d375093b32f8d35d0b1069a66a0330d7481812e31d07506bad1404b0df480811e33d0d3067ade401b0db4dd404daebe34d640c71a689a81ce34d0c82f1cd6de8bc73ca73f9fd16958ff11d2fd8c41bb60bac369f4ffcb7c9ffcfe2f1b4c67a9'

ISP_PROG = binascii.unhexlify(ISP_PROG)

print('ISP_FLASH progam size (compressed)', len(ISP_PROG))
ISP_PROG = zlib.decompress(ISP_PROG)
print('ISP_FLASH progam size (decompressed)', len(ISP_PROG))

def slip_reader(port):
    partial_packet = None
    in_escape = False

    while True:
        waiting = port.inWaiting()
        read_bytes = port.read(1 if waiting == 0 else waiting)
        if read_bytes == b'':
            raise Exception("Timed out waiting for packet %s" % ("header" if partial_packet is None else "content"))
        for b in read_bytes:

            if type(b) is int:
                b = bytes([b])  # python 2/3 compat

            if partial_packet is None:  # waiting for packet header
                if b == b'\xc0':
                    partial_packet = b""
                else:
                    raise Exception('Invalid head of packet (%r)' % b)
            elif in_escape:  # part-way through escape sequence
                in_escape = False
                if b == b'\xdc':
                    partial_packet += b'\xc0'
                elif b == b'\xdd':
                    partial_packet += b'\xdb'
                else:
                    raise Exception('Invalid SLIP escape (%r%r)' % (b'\xdb', b))
            elif b == b'\xdb':  # start of escape sequence
                in_escape = True
            elif b == b'\xc0':  # end of packet
                yield partial_packet
                partial_packet = None
            else:  # normal byte in packet
                partial_packet += b


class ISPResponse:
    class ISPOperation(Enum):
        ISP_ECHO = 0xC1
        ISP_NOP = 0xC2
        ISP_MEMORY_WRITE = 0xC3
        ISP_MEMORY_READ = 0xC4
        ISP_MEMORY_BOOT = 0xC5
        ISP_DEBUG_INFO = 0xD1

    class ErrorCode(Enum):
        ISP_RET_DEFAULT = 0
        ISP_RET_OK = 0xE0
        ISP_RET_BAD_DATA_LEN = 0xE1
        ISP_RET_BAD_DATA_CHECKSUM = 0xE2
        ISP_RET_INVALID_COMMAND = 0xE3

    @staticmethod
    def parse(data):
        op = data[0]
        reason = data[1]
        text = ''
        try:
            if ISPResponse.ISPOperation(op) == ISPResponse.ISPOperation.ISP_DEBUG_INFO:
                text = data[2:].decode()
        except ValueError:
            print('Warning: recv unknown op', op)

        return (op, reason, text)


class FlashModeResponse:
    class Operation(Enum):
        ISP_DEBUG_INFO = 0xD1
        ISP_NOP = 0xD2
        ISP_FLASH_ERASE = 0xD3
        ISP_FLASH_WRITE = 0xD4
        ISP_REBOOT = 0xD5
        ISP_UARTHS_BAUDRATE_SET = 0xD6
        FLASHMODE_FLASH_INIT = 0xD7

    class ErrorCode(Enum):
        ISP_RET_DEFAULT = 0
        ISP_RET_OK = 0xE0
        ISP_RET_BAD_DATA_LEN = 0xE1
        ISP_RET_BAD_DATA_CHECKSUM = 0xE2
        ISP_RET_INVALID_COMMAND = 0xE3

    @staticmethod
    def parse(data):
        op = data[0]
        reason = data[1]
        text = ''
        if FlashModeResponse.Operation(op) == FlashModeResponse.Operation.ISP_DEBUG_INFO:
            text = data[2:].decode()

        return (op, reason, text)


def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]


class MAIXLoader:
    def change_baudrate(self, baudrate):
        self._port = serial.Serial(
            port=port,
            baudrate=baudrate,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS,
            timeout=3
        )

    def __init__(self, port='/dev/ttyUSB1', baudrate=115200):
        # configure the serial connections (the parameters differs on the device you are connecting to)
        self._port = serial.Serial(
            port=port,
            baudrate=baudrate,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS
        )
        print('BAUDRATE', baudrate)

        self._port.isOpen()
        self._slip_reader = slip_reader(self._port)

    """ Read a SLIP packet from the serial port """

    def read(self):
        return next(self._slip_reader)

    """ Write bytes to the serial port while performing SLIP escaping """

    def write(self, packet):
        buf = b'\xc0' \
              + (packet.replace(b'\xdb', b'\xdb\xdd').replace(b'\xc0', b'\xdb\xdc')) \
              + b'\xc0'
        print('[WRITE]', binascii.hexlify(buf))
        return self._port.write(buf)

    def read_loop(self):
        out = b''
        # while self._port.inWaiting() > 0:
        #     out += self._port.read(1)

        # print(out)
        while 1:
            sys.stdout.write('[RECV] raw data: ')
            sys.stdout.write(binascii.hexlify(self._port.read(1)).decode())
            sys.stdout.flush()

    def recv_one_return(self):
        data = b''
        # find start boarder
        sys.stdout.write('[RECV one return] raw data: ')
        while 1:
            c = self._port.read(1)
            sys.stdout.write(binascii.hexlify(c).decode())
            sys.stdout.flush()
            if c == b'\xc0':
                break

        in_escape = False
        while 1:
            c = self._port.read(1)
            sys.stdout.write(binascii.hexlify(c).decode())
            sys.stdout.flush()
            if c == b'\xc0':
                break

            elif in_escape:  # part-way through escape sequence
                in_escape = False
                if c == b'\xdc':
                    data += b'\xc0'
                elif c == b'\xdd':
                    data += b'\xdb'
                else:
                    raise Exception('Invalid SLIP escape (%r%r)' % (b'\xdb', b))
            elif c == b'\xdb':  # start of escape sequence
                in_escape = True

            data += c

        sys.stdout.write('\n')
        return data

    def reset_to_isp(self):
        self._port.dtr = False
        self._port.rts = False
        time.sleep(0.01)
        print('-- RESET to LOW, IO16 to HIGH --')
        # Pull reset down and keep 10ms
        self._port.dtr = False
        self._port.rts = True
        time.sleep(0.01)
        print('-- IO16 to LOW, RESET to HIGH --')
        # Pull IO16 to low and release reset
        self._port.rts = False
        self._port.dtr = True
        time.sleep(0.01)

    def reset_to_boot(self):
        self._port.dtr = False
        self._port.rts = False
        time.sleep(0.01)
        print('-- RESET to LOW --')
        # Pull reset down and keep 10ms
        self._port.dtr = False
        self._port.rts = True
        time.sleep(0.01)
        print('-- RESET to HIGH, BOOT --')
        # Pull IO16 to low and release reset
        self._port.rts = False
        self._port.dtr = False
        time.sleep(0.01)

    def greeting(self):
        self._port.write(b'\xc0\xc2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0')
        op, reason, text = ISPResponse.parse(self.recv_one_return())

        print('MAIX return op:', ISPResponse.ISPOperation(op).name, 'reason:', ISPResponse.ErrorCode(reason).name)

    def flash_greeting(self):
        while 1:
            self._port.write(b'\xc0\xd2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0')
            op, reason, text = FlashModeResponse.parse(self.recv_one_return())
            print('MAIX return op:', FlashModeResponse.Operation(op).name, 'reason:',
                  FlashModeResponse.ErrorCode(reason).name)
            if FlashModeResponse.Operation(op) == FlashModeResponse.Operation.ISP_NOP:
                print("[DEBUG] Boot to flashmode successfully")
                break

            print('wait 1 sec')
            time.sleep(1)

    def boot(self, address=0x80000000):
        print('[INFO] Boot from', '0x' + hex(address))
        out = struct.pack('II', address, 0)

        crc32_checksum = struct.pack('I', binascii.crc32(out) & 0xFFFFFFFF)

        out = struct.pack('HH', 0xc5, 0x00) + crc32_checksum + out  # op: ISP_MEMORY_WRITE: 0xc3
        self.write(out)

    def recv_debug(self):
        op, reason, text = ISPResponse.parse(self.recv_one_return())
        print('[RECV] op:', ISPResponse.ISPOperation(op).name, 'reason:', ISPResponse.ErrorCode(reason).name)
        if text:
            print('-' * 30)
            print(text)
            print('-' * 30)
        if ISPResponse.ErrorCode(reason) not in (ISPResponse.ErrorCode.ISP_RET_DEFAULT, ISPResponse.ErrorCode.ISP_RET_OK):
            print('Failed, retry, errcode=', hex(reason))
            return False
        return True

    def flash_recv_debug(self):
        op, reason, text = FlashModeResponse.parse(self.recv_one_return())
        print('[Flash-RECV] op:', FlashModeResponse.Operation(op).name, 'reason:',
              FlashModeResponse.ErrorCode(reason).name)
        if text:
            print('-' * 30)
            print(text)
            print('-' * 30)

        if FlashModeResponse.ErrorCode(reason) not in (FlashModeResponse.ErrorCode.ISP_RET_OK, FlashModeResponse.ErrorCode.ISP_RET_OK):
            print('Failed, retry')
            return False
        return True

    def init_flash(self, chip_type):
        chip_type = int(chip_type)
        print("[INFO] Select flash", chip_type)
        out = struct.pack('II', chip_type, 0)
        crc32_checksum = struct.pack('I', binascii.crc32(out) & 0xFFFFFFFF)

        out = struct.pack('HH', 0xd7, 0x00) + crc32_checksum + out

        sent = self.write(out)
        op, reason, text = FlashModeResponse.parse(self.recv_one_return())
        print('MAIX return op:', FlashModeResponse.Operation(op).name, 'reason:',
              FlashModeResponse.ErrorCode(reason).name)

    def flash_dataframe(self, data, address=0x80000000):
        DATAFRAME_SIZE = 1024
        data_chunks = chunks(data, DATAFRAME_SIZE)
        print('[DEBUG] flash dataframe | data length:', len(data))

        for i, chunk in enumerate(data_chunks):
            while 1:
                print('[INFO] sending chunk', i, '@address', hex(address), 'chunklen', len(chunk))
                out = struct.pack('II', address, len(chunk))

                crc32_checksum = struct.pack('I', binascii.crc32(out + chunk) & 0xFFFFFFFF)

                out = struct.pack('HH', 0xc3, 0x00) + crc32_checksum + out + chunk  # op: ISP_MEMORY_WRITE: 0xc3
                sent = self.write(out)
                print('[INFO]', 'sent', sent, 'bytes', 'checksum', binascii.hexlify(crc32_checksum).decode())

                address += len(chunk)

                if self.recv_debug():
                    break

    def dump_to_flash(self, data, address=0):
        '''
        typedef struct __attribute__((packed)) {
            uint8_t op;
            int32_t checksum; // 下面的所有字段都要参与checksum的计算
            uint32_t address;
            uint32_t data_len;
            uint8_t data_buf[1024];
        } isp_request_t;
        '''

        DATAFRAME_SIZE = 4096
        data_chunks = chunks(data, DATAFRAME_SIZE)
        print('[DEBUG] flash dataframe | data length:', len(data))

        for i, chunk in enumerate(data_chunks):
            print('[INFO] sending chunk', i, '@address', hex(address))
            out = struct.pack('II', address, len(chunk))

            crc32_checksum = struct.pack('I', binascii.crc32(out + chunk) & 0xFFFFFFFF)

            out = struct.pack('HH', 0xd4, 0x00) + crc32_checksum + out + chunk
            print("[$$$$]", binascii.hexlify(out[:32]).decode())
            sent = self.write(out)
            print('[INFO]', 'sent', sent, 'bytes', 'checksum', crc32_checksum)
            self.flash_recv_debug()
            address += len(chunk)

    def flash_erase(self):
        print('[DEBUG] erasing spi flash.')
        self._port.write(b'\xc0\xd3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0')
        op, reason, text = FlashModeResponse.parse(self.recv_one_return())
        print('MAIX return op:', FlashModeResponse.Operation(op).name, 'reason:',
              FlashModeResponse.ErrorCode(reason).name)

    def install_flash_bootloader(self, data):
        # 1. 刷入 flash bootloader
        self.flash_dataframe(data, address=0x80000000)

    def flash_firmware(self, firmware_bin: bytes, aes_key: bytes = None):
        print('[DEBUG] flash_firmware DEBUG: aeskey=', aes_key)

        # 固件加上头
        # 格式: SHA256(after)(32bytes) + AES_CIPHER_FLAG (1byte) + firmware_size(4bytes) + firmware_data

        aes_cipher_flag = b'\x01' if aes_key else b'\x00'

        # 加密
        if aes_key:
            firmware_bin = AES.new(key=aes_key, mode=AES.MODE_CBC, iv=b'\x00' * 16).encrypt(firmware_bin)

        firmware_len = len(firmware_bin)

        data = aes_cipher_flag + struct.pack('I', firmware_len) + firmware_bin

        sha256_hash = hashlib.sha256(data).digest()

        firmware_with_header = data + sha256_hash

        # 3. 分片刷入固件
        data_chunks = chunks(firmware_with_header, 4096)  # 4kb for a sector

        for n, chunk in enumerate(data_chunks):
            chunk = chunk.ljust(4096, b'\x00')  # align by 4kb

            # 3.1 刷入一个dataframe
            print('[INFO]', 'Write firmware data piece')
            self.dump_to_flash(chunk, address=n * 4096)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("-d", "--device", help="UART device", default="/dev/ttyUSB0")
    parser.add_argument("-c", "--chip", help="SPI Flash type, 1 for in-chip, 0 for on-board", default=1)
    parser.add_argument("-b", "--baudrate", type=int, help="UART baudrate for uploading firmware", default=115200)
    parser.add_argument("-l", "--bootloader", help="bootloader bin path", required=False, default=None)
    parser.add_argument("-k", "--key", help="AES key in hex, if you need encrypt your firmware.", required=False, default=None)
    parser.add_argument("-v", "--verbose", help="increase output verbosity", default=False,
                        action="store_true")
    parser.add_argument("firmware", help="firmware bin path")

    args = parser.parse_args()

    loader = MAIXLoader(port=args.device, baudrate=args.baudrate)

    # 1. Greeting.
    loader.reset_to_isp()
    while 1:
        try:
            print('Greeting')
            loader.greeting()
            break
        except serial.SerialTimeoutException:
            print('Timeout, retry to greeting.')

    # 2. flash bootloader and firmware
    firmware_bin = open(args.firmware, 'rb')

    # install bootloader at 0x80000000
    if args.bootloader:
        loader.install_flash_bootloader(open(args.bootloader, 'rb').read())
    else:
        loader.install_flash_bootloader(ISP_PROG)

    loader.boot()

    print('Sleep 2 seconds to wait isp_flash is booted.')

    time.sleep(2)

    loader.flash_greeting()

    loader.init_flash(args.chip)

    if args.key:
        aes_key = binascii.a2b_hex(args.key)
        if len(aes_key) != 16:
            raise ValueError('AES key must by 16 bytes')

        loader.flash_firmware(firmware_bin.read(), aes_key=aes_key)
    else:
        loader.flash_firmware(firmware_bin.read())

    # 3. boot
    loader.reset_to_boot()
